package src

import (
	"fmt"
	"os"
	"syscall"
)

// 内存映射文件 （Memory-mapped file）将一段虚拟内存逐字节对应于一个文件或类文件资源，使得应用程序处理映射部分如同访问主存，用于增加 I/O 性能。
// Mmap 函数存在于 Go's syscall package 中，他接收一个文件描述符，需要映射的大小（返回的切边容量）以及需要的内存保护和映射类型

func LinuxMmap(path string) {
	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	stat, err := os.Stat(path)
	if err != nil {
		panic(err)
	}

	size, err := LMmapSize(int(stat.Size()))
	if err != nil {
		panic(err)
	}

	// error 内存地址 ([signal SIGBUS: bus error code=0x2 addr=0x13ac000 pc=0x10c1375])
	// SIGBUS 信号意味着你在文件的地址以外写入内容
	// 在创建新文件时，它最初为空，即大小为0字节，您需要使用 ftruncate 调整其大小，至少足以包含写入的地址 （可能四舍五入到页面大小）
	if err = syscall.Ftruncate(int(file.Fd()), int64(size)); err != nil {
		panic(err)
	}

	b, err := syscall.Mmap(int(file.Fd()), 0, size, syscall.PROT_WRITE|syscall.PROT_READ, syscall.MAP_SHARED)
	if err != nil {
		panic(err)
	}

	// for index, bb := range []byte("Hello world") {
	// 	b[index] = bb
	// }

	if err = syscall.Munmap(b); err != nil {
		panic(err)
	}
}

func LMmapSize(size int) (int, error) {
	// Double the size from 32KB until 1GB.
	for i := uint(15); i <= 30; i++ {
		if size <= 1<<i {
			return 1 << i, nil
		}
	}

	// Verify the requested size is not above the maximum allowed.
	if size > maxMapSize {
		return 0, fmt.Errorf("mmap too large")
	}

	// if larger than 1GB then grow by 1GB at a time.
	sz := int64(size)
	if remainder := sz % int64(maxMmapStep); remainder > 0 {
		sz += int64(maxMmapStep) - remainder
	}

	// Ensure that the mmap size is a multiple of the page size.
	// This should always ne true since we're incrementing in MBs.
	pageSize := int64(os.Getpagesize())
	if (sz % pageSize) != 0 {
		sz = ((sz / pageSize) + 1) * pageSize
	}

	// If we've exceeded the max size then only grow up to the max size.
	if sz > maxMapSize {
		sz = maxMapSize
	}

	return int(sz), nil
}

// 读取文件
// 读取文件直接以只读方式将文件映射到主存中即可
func LRead(path string) {
	file, err := os.OpenFile(path, os.O_RDWR, 0600)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	stat, err := os.Stat(path)
	if err != nil {
		panic(err)
	}

	b, err := syscall.Mmap(int(file.Fd()), 0, int(stat.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
	if err != nil {
		panic(err)
	}
	defer syscall.Munmap(b)

	fmt.Println(string(b))
}
